﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Browser;

namespace System.Windows.Controls
{
    /// <summary>
    /// Implements a LinkLabel which supports text bounding, such that mouse hits do not occur in negative space in the link, and such that
    /// text wrapping is provided as well.
    /// </summary>
    [TemplatePart(Name="Text", Type=typeof(TextBlock))]
    public class LinkLabel : System.Windows.Controls.ControlBase
    {
        /// <summary>
        /// An event that fires when the <see cref="LinkLabel"/> has been clicked with the left mouse button
        /// </summary>
        public event EventHandler   Click = null;

        /// <summary>
        /// Instantiates a <see cref="LinkLabel"/> instance.
        /// </summary>
        public LinkLabel()
        {
            this.DefaultStyleKey = typeof(LinkLabel);
            this.TextAlignment = TextAlignment.Left;
            this.TextWrapping = TextWrapping.NoWrap;

            this.Loaded += new RoutedEventHandler(OnLoaded);
        }

        void OnLoaded(object sender, RoutedEventArgs e)
        {
            if (text != null) text.Text = String.IsNullOrEmpty(this.Text) ? String.Empty : this.Text;
        }

        /// <summary>
        /// Identifies the <see cref="NavigateUri"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty   NavigateUriProperty = RegisterDependencyProperty(typeof(LinkLabel), "NavigateUri", typeof(Uri));

        /// <summary>
        /// Identifies the <see cref="TargetName"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty   TargetNameProperty = RegisterDependencyProperty(typeof(LinkLabel), "TargetName", typeof(string));

        /// <summary>
        /// Identifies the <see cref="TextAlignment"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty   TextAlignmentProperty = RegisterDependencyProperty(typeof(LinkLabel), "TextAlignment", typeof(TextAlignment));

        /// <summary>
        /// Identifies the <see cref="TextWrapping"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty   TextWrappingProperty = RegisterDependencyProperty(typeof(LinkLabel), "TextWrapping", typeof(TextWrapping));

        /// <summary>
        /// Identifies the <see cref="Text"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty   TextProperty = RegisterDependencyProperty(typeof(LinkLabel), "Text", typeof(string));

        private TextBlock   text;

        /// <summary>
        /// (Override) Handles dependency property changes within the <see cref="LinkLabel"/> control instance.
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> argument for the event</param>
        protected override void OnDependencyPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            if (e.Property == NavigateUriProperty)
            {
                this.NavigateUri = e.NewValue as Uri;
            }
            else if (e.Property == TargetNameProperty)
            {
                this.TargetName = (e.NewValue == null ? String.Empty : e.NewValue.ToString());
            }
            else if (e.Property == TextAlignmentProperty)
            {
                this.TextAlignment = (TextAlignment)e.NewValue;
            }
            else if (e.Property == TextWrappingProperty)
            {
                this.TextWrapping = (TextWrapping)e.NewValue;
            }
            else if (e.Property == TextProperty)
            {
                this.Text = (e.NewValue == null ? String.Empty : e.NewValue.ToString());
            }
            else
            {
                base.OnDependencyPropertyChanged(e);
            }
        }

        /// <summary>
        /// Gets / Sets the Text for the LinkLabel.
        /// </summary>
        public string Text
        {
            get { object val = GetValue(TextProperty); return (val != null ? val.ToString() : String.Empty); }
            set { SetValue(TextProperty, value); }
        }

        /// <summary>
        /// Gets/Sets the Internet Uri for the LinkLabel.
        /// </summary>
        public Uri NavigateUri
        {
            get { return (Uri)GetValue(NavigateUriProperty); }
            set { SetValue(NavigateUriProperty, value); }
        }

        /// <summary>
        /// Gets/Sets the Internet Uri for the LinkLabel.
        /// </summary>
        public string TargetName
        {
            get { object val = GetValue(TargetNameProperty); return (val == null ? String.Empty : val.ToString()); }
            set { SetValue(TargetNameProperty, value); }
        }

        /// <summary>
        /// Gets/Sets the alignment of the text in the LinkLabel.
        /// </summary>
        public TextAlignment TextAlignment
        {
            get { return (TextAlignment)GetValue(TextAlignmentProperty); }
            set { SetValue(TextAlignmentProperty, value); if (text != null) text.TextAlignment = value; }
        }

        /// <summary>
        /// Gets/Sets the alignment of the text in the LinkLabel.
        /// </summary>
        public TextWrapping TextWrapping
        {
            get { return (TextWrapping)GetValue(TextWrappingProperty); }
            set { SetValue(TextWrappingProperty, value); if (text != null) text.TextWrapping = value; }
        }

        /// <summary>
        /// (Override) Processes the control template for this <see cref="LinkLabel"/> instance.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            
            text = VerifyTemplateChild<TextBlock>("Text");
            if (text != null)
            {
                text.Text = this.Text;
                text.TextAlignment = this.TextAlignment;
                text.TextWrapping = this.TextWrapping;
            }
        }

        /// <summary>
        /// (Override) Handles the Mouse Enter event for the <see cref="LinkLabel"/>.
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.Input.MouseEventArgs"/> for the event</param>
        protected override void OnMouseEnter(MouseEventArgs e)
        {
            base.OnMouseEnter(e);
            if (this.IsEnabled && text != null)
            {
                Point pt = e.GetPosition(text);
                Rect rect = LayoutHelper.GetBounds(text);
                if (rect.Contains(pt)) 
                    GoToState(true, "MouseOver");
            }
        }

        /// <summary>
        /// (Override) Handles the Mouse Leave event for the <see cref="LinkLabel"/>.
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.Input.MouseEventArgs"/> for the event</param>
        protected override void OnMouseLeave(MouseEventArgs e)
        {
            base.OnMouseLeave(e);
            if (this.IsEnabled) GoToState(true, "Normal");
        }

        /// <summary>
        /// (Override) Handles the Mouse Move event for the <see cref="LinkLabel"/>.
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.Input.MouseEventArgs"/> for the event</param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (this.IsEnabled && text != null)
            {
                Point pt = e.GetPosition(text);
                Rect rect = LayoutHelper.GetBounds(text);
                GoToState(true, (rect.Contains(pt) ? "MouseOver" : "Normal"));
            }
        }

        /// <summary>
        /// (Override) Handles the Mouse Left Button Down event for the <see cref="LinkLabel"/>.
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> for the event</param>
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);
            if (this.IsEnabled && text != null)
            {
                this.CaptureMouse();

                Point pt = e.GetPosition(text);
                Rect rect = LayoutHelper.GetBounds(text);
                if (rect.Contains(pt))
                {
                    GoToState(true, "Pressed");
                }
            }
        }

        /// <summary>
        /// (Override) Handles the Mouse Left Button Up event for the <see cref="LinkLabel"/>.
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> for the event</param>
        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonUp(e);
            if (this.IsEnabled && text != null)
            {
                Point pt = e.GetPosition(this);
                this.ReleaseMouseCapture();

                Rect rect = LayoutHelper.GetBounds(text);
                if (rect.Contains(pt))
                {
                    GoToState(true, "MouseOver");
                    OnClick();
                }
                else
                {
                    GoToState(true, "Normal");
                }
            }
        }

        private bool IsSafeScheme(string scheme)
        {
            if ((!scheme.Equals("http", StringComparison.OrdinalIgnoreCase) 
                && !scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) 
                && ((!scheme.Equals("file", StringComparison.OrdinalIgnoreCase) 
                && !scheme.Equals("mailto", StringComparison.OrdinalIgnoreCase)) 
                && !scheme.Equals("mms", StringComparison.OrdinalIgnoreCase)))
            {
                return false;
            }

            return true;
        }

        private Uri GetAbsoluteUri()
        {
            Uri navigateUri = this.NavigateUri;
            if (navigateUri.IsAbsoluteUri) return navigateUri;

            if (Application.Current == null)
            {
                throw new NotSupportedException(SystemResources.ERR_LINK_NOAPPLICATION);
            }

            return new Uri(Application.Current.Host.Source, navigateUri);
        }

        private void Navigate()
        {
            Uri absoluteUri = this.GetAbsoluteUri();
            string targetName = string.IsNullOrEmpty(this.TargetName) ? "_self" : this.TargetName;

            if (!absoluteUri.IsAbsoluteUri)
            {
                throw new InvalidOperationException(String.Format(SystemResources.ERR_INVALID_LINK_URI, absoluteUri.ToString()));
            }

            try
            {
                if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
                    OpenUrl(absoluteUri, "_blank");
                else
                    OpenUrl(absoluteUri, targetName);
            }
            catch (InvalidOperationException exception)
            {
                throw new InvalidOperationException(String.Format(SystemResources.ERR_LINK_FAILED, absoluteUri.ToString()), exception);
            }
        }

        /// <summary>
        /// A virtual which dispatches the <see cref="Click"/> event.
        /// </summary>
        protected virtual void OnClick()
        {
            if (this.NavigateUri != null && !System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
            {
                this.Navigate();
            }

            if (null != this.Click) this.Click(this, EventArgs.Empty);
        }

        /// <summary>
        /// (Override) Executes when the <see cref="LinkLabel"/> receives focus.
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> argument for the event</param>
        protected override void OnGotFocus(RoutedEventArgs e)
        {
            base.OnGotFocus(e);
            if (this.IsEnabled) GoToState(true, "Focused");
        }

        /// <summary>
        /// (Override) Executes when the <see cref="LinkLabel"/> loses focus.
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> argument for the event</param>
        protected override void OnLostFocus(RoutedEventArgs e)
        {
            base.OnLostFocus(e);
            if (this.IsEnabled) GoToState(true, "Unfocused");
        }

        //Hack for Opening in new tabs
        private static HtmlElement ghostAnchor = null;

        /// <summary>
        /// A static helper method for opening a Uri with a given Browser Window target.
        /// </summary>
        public static void OpenUrl(Uri uri, string targetName)
        {
            HtmlElement ghostLink = VerifyGhostAnchor();
            if (ghostLink != null)
            {
                ghostLink.SetAttribute("href", uri.ToString());
                ghostLink.SetAttribute("target", targetName);
                ghostLink.Invoke("click");
            }
            else
            {
                HtmlPage.Window.Navigate(uri);
            }
        }

        private static HtmlElement VerifyGhostAnchor()
        {
            if (ghostAnchor == null)
            {
                ghostAnchor = HtmlPage.Document.CreateElement("a");
                ghostAnchor.SetAttribute("target", "_blank");
                ghostAnchor.SetStyleAttribute("display", "none");
                HtmlPage.Document.Body.AppendChild(ghostAnchor);
            }

            return ghostAnchor;
        }
    }
}
